package com.biodiv.community.filter;

import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.biodiv.community.constant.CommonConstant.TRACE_ID;

/**
 * @author tianquan
 */
@Slf4j
@Component
public class LogFilter implements GlobalFilter, Ordered {

    private static final String START_TIME = "startTime";

    private static final List<String> INCLUDED_HEADERS = Arrays.asList(
            "content-type", "authorization"
    );

    private static final List<String> BLACKLIST_PARAMS = Arrays.asList("");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 记录开始时间
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        ServerHttpRequest request = exchange.getRequest();

        // 2. 过滤文件流请求（避免读取二进制 body）
        if (request.getHeaders().getContentType() != null &&
                (request.getHeaders().getContentType().toString().startsWith("multipart/") ||
                        request.getHeaders().getContentType().toString().startsWith("application/octet-stream"))) {
            return logAndContinue(exchange, chain, request, "【文件流请求，跳过 body 打印】");
        }

        // 3. 读取并缓存请求体，通过 ServerHttpRequestDecorator 重新写入（核心修复）
        return DataBufferUtils.join(request.getBody())
                .defaultIfEmpty(exchange.getResponse().bufferFactory().wrap(new byte[0]))
                .flatMap(dataBuffer -> {
                    // 3.1 复制 DataBuffer（避免原 buffer 被释放后无法读取）
                    DataBuffer copiedBuffer = exchange.getResponse().bufferFactory().wrap(dataBuffer.asByteBuffer());
                    // 3.2 读取 body 字符串（用于日志打印）
                    String body = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer()).toString();
                    // 3.3 释放原 DataBuffer（避免内存泄漏）
                    DataBufferUtils.release(dataBuffer);

                    // 3.4 装饰请求：重写 getBody() 方法，返回缓存的 copiedBuffer（关键！）
                    ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(request) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            // 返回复制后的 buffer，后续过滤器可重复读取
                            return Flux.just(copiedBuffer);
                        }
                    };

                    // 3.5 打印请求日志
                    log.info("""
                                    
                                    ============================== 请求开始 ===========================
                                    - Path: {}{}
                                    - Headers: {}
                                    - Parameters: {}
                                    - Body: {}""",
                            request.getMethod(), request.getURI().getPath(),
                            getHeaders(request),
                            getParams(request),
                            body
                    );

                    // 3.6 用装饰后的请求继续过滤器链
                    return chain.filter(exchange.mutate().request(decoratedRequest).build())
                            // 3.7 释放 copiedBuffer（请求处理完成后）
                            .doFinally(signalType -> DataBufferUtils.release(copiedBuffer))
                            // 3.8 打印响应耗时
                            .then(Mono.fromRunnable(() -> logResponseTime(exchange, request)));
                });
    }

    private Mono<Void> logAndContinue(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, String body) {
        log.info("""
                        
                        ============================== 请求开始 ===========================
                        - Path: {}{}
                        - Headers: {}
                        - Parameters: {}
                        - Body: {}
                        """,
                request.getMethod(), request.getURI().getPath(),
                getHeaders(request),
                getParams(request),
                body
        );

        return chain.filter(exchange)
                .then(Mono.fromRunnable(() -> logResponseTime(exchange, request)));
    }

    private void logResponseTime(ServerWebExchange exchange, ServerHttpRequest request) {
        Long startTime = exchange.getAttribute(START_TIME);
        if (startTime != null) {
            String traceId = request.getHeaders().getFirst(TRACE_ID);
            MDC.put(TRACE_ID, traceId);
            long duration = System.currentTimeMillis() - startTime;
            log.info(" 请求结束: {} 耗时: {} ms",
                    request.getMethod().name() + request.getURI().getPath(), duration);
        }
        // 清除 MDC（避免线程复用导致 traceId 污染）
        MDC.clear();
    }

    public static String getHeaders(ServerHttpRequest request) {
        Map<String, String> headers = new HashMap<>();
        request.getHeaders().forEach((headerName, values) -> {
            if (INCLUDED_HEADERS.contains(headerName.toLowerCase())) {
                headers.put(headerName, values.get(0));
            }
        });
        return JSONObject.toJSONString(headers);
    }

    public static String getParams(ServerHttpRequest request) {
        Map<String, Object> params = new HashMap<>();
        request.getQueryParams().forEach((paramName, values) -> {
            params.put(paramName, values.size() > 1 ? values : values.get(0));
        });
        BLACKLIST_PARAMS.forEach(params::remove);
        return JSONObject.toJSONString(params);
    }

    @Override
    public int getOrder() {
        // 确保在路由过滤器（如 NettyRoutingFilter）之前执行，提前缓存 body
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}